/*
MIT License

Copyright (c) 2022 МГТУ им. Н.Э. Баумана, кафедра ИУ-6, Михаил Фетисов,

https://bmstu.codes/lsx/simodo/loom
*/

#include "simodo/interpret/builtins/hosts/base/BaseMachine.h"
#include "simodo/interpret/builtins/hosts/base/BaseInterpret_abstract.h"
#include "simodo/interpret/AnalyzeException.h"
#include "simodo/variable/FunctionWrapper.h"
#include "simodo/inout/convert/functions.h"
#include "simodo/inout/format/fmt.h"
#include "simodo/inout/token/LexemeType.h"

#include <cassert>

namespace
{
    static const simodo::variable::Variable error = simodo::variable::error_variable();
}

namespace simodo::interpret::builtins
{

BaseMachine::BaseMachine(const inout::uri_set_t & files)
    : _files(files)
    , _stack()
    , _return_variable(variable::null_variable())
{
}

void BaseMachine::reset()
{
    /// \todo BaseMachine::reset()
}

void BaseMachine::push(variable::Variable v)
{
    _stack.push(v);
}

void BaseMachine::pushRef(const variable::Variable & v, const inout::TokenLocation & location)
{
    const variable::Variable & o = v.origin();

    // _stack.push({ o.name(), variable::VariableRef {o}, location(), {} });
    _stack.push( variable::Variable { variable::VariableRef(o), location } );
}

void BaseMachine::pushRef(variable::VariableRef ref)
{
    // _stack.push({ ref.origin().name(), ref, ref.origin().location()(), {} });
    _stack.push( { ref, ref.origin().location() } );
}

variable::Variable BaseMachine::pop()                           
{ 
    return _stack.pop(); 
} 

void BaseMachine::popAmount(size_t n)
{
    _stack.popAmount(n); 
}

variable::VariableRef BaseMachine::findVariable(const inout::Token & variable_name) const
{
    /// \todo Не забыть переделать под ограничения фреймов вызовов и другие механизмы

    variable::VariableSetWrapper search_area(_stack.stack(), 0, _stack.size());

    variable::VariableRef ref = find(search_area, variable_name.lexeme());

    if (ref.origin().name().empty())
        throw AnalyzeException("BaseMachine::findVariable", 
                                variable_name.makeLocation(_files),
                                "Переменная '" + inout::toU8(variable_name.lexeme()) + "' не найдена");

    return ref;
}

variable::VariableRef BaseMachine::find(variable::VariableSetWrapper & search_area, const std::u16string & variable_name) const
{
    // Поиск ведётся с конца, чтобы корректно работал поиск переменных по истории их объявления с учётом блоков кода

    for(size_t i=search_area.size()-1; i < search_area.size(); --i)
        if (search_area[i].name() == variable_name)
            return search_area[i].makeReference();

    return error.makeReference();
}

void BaseMachine::checkProcedureCalled()
{
    /// \todo BaseMachine::checkProcedureCalled()
}

variable::Variable BaseMachine::createVariable_implicit(const inout::Token & constant_value) const
{
    switch(constant_value.type())
    {
    case inout::LexemeType::Annotation:
        return { u"", constant_value.lexeme(), constant_value.location(), {} };
    case inout::LexemeType::Number:
        if (constant_value.qualification() == inout::TokenQualification::Integer)
            return { u"", static_cast<int64_t>(stoll(inout::toU8(constant_value.lexeme()))), constant_value.location(), {} };
        return { u"", stod(inout::toU8(constant_value.lexeme())), constant_value.location(), {} };
    case inout::LexemeType::Punctuation:
        if (constant_value.lexeme() == u"true" || constant_value.lexeme() == u"false")
            return { u"", constant_value.lexeme() == u"true", constant_value.location(), {} };
        if (constant_value.lexeme() == u"undef")
            return { u"", variable::ValueType::Null, constant_value.location(), {} };
        if (constant_value.lexeme() == u"null")
            return { u"", variable::ValueType::Null, constant_value.location(), {} };
    default:
        break;
    }

    throw bormental::DrBormental("BaseMachine::createVariable_implicit", 
                    inout::fmt("Invalid internal constant type to convert (%1)")
                    .arg(getLexemeTypeName(constant_value.type())));
}

variable::Value BaseMachine::callFunction_implicit(const inout::Token & , size_t args_stack_position)
{
    const variable::Variable & function_origin = _stack.at(args_stack_position-1).origin();

    if (function_origin.type() == variable::ValueType::Null)
        return {};

    if (function_origin.type() != variable::ValueType::Function) {
        // Variable                    function = convertVariable_overridable(_stack.at(args_stack_position-1), ValueType::Function);
        // FunctionWrapper             wrapper(*this, dot, function);
        // VariableSetWrapper_mutable  arguments(_stack.stack(), args_stack_position, _stack.size());

        // return wrapper.invoke(arguments, host().getSemanticType());
        return {};
    }

    variable::FunctionWrapper            wrapper(function_origin);
    variable::VariableSetWrapper_mutable arguments(_stack.stack(), args_stack_position, _stack.size());

    return wrapper.invoke(arguments);
}

variable::Variable BaseMachine::getElement_implicit(const inout::Token & , const inout::Token & variable_name) 
{
    // Источник элемента: top(0)

    if (_stack.back().origin().type() == variable::ValueType::Null)
        return variable::error_variable(variable_name.location());

    std::shared_ptr<variable::Object> record = _stack.back().origin().value().getObject();
    
    const variable::Variable & element = record->getVariableByName(variable_name.lexeme());

    if (element.type() != variable::ValueType::Null)
        // Создаём ссылку на элемент (VariableRef).
        // Возвращая имя, разрешается использовать элемент в левой части инициализации/присваивания 
        return { element.makeReference(), variable_name.location() };
    
    throw AnalyzeException("BaseMachine::getElement_implicit", 
                           variable_name.makeLocation(_files),
                           "Member '" + inout::toU8(variable_name.lexeme()) + "' not found");
}

}